package surface;

import utils.*;

//  ----------------------------------------------------------------------------
//  construct a PolylinePathway given the number of points (vertices),
//  an array of points, and a path radius.
public class PolylinePathway extends Pathway {

	public void cachePathStats() {
		lengths = new float[points.length];
		normals = new Vector3[points.length];
		for (int i = 1; i < points.length; i++) {
			normals[i] = new Vector3();
			normals[i].setDiff(points[i], points[i - 1]);
			lengths[i] = normals[i].magnitude();
			normals[i].setScale(1.0F / lengths[i], normals[i]);
		}

	}

	public boolean mapPointToPath(Vector3 point, Vector3 onPath, Vector3 tangent) {
		float minDistance = 3.402823E+38F;
		for (int i = 1; i < points.length; i++) {
			segmentLength = lengths[i];
			segmentNormal = normals[i];
			float d = pointToSegmentDistance(point, points[i - 1], points[i]);
			if (d < minDistance) {
				minDistance = d;
				onPath.set(chosen);
				tangent.set(segmentNormal);
			}
		}

		return point.approximateDistance(onPath) < radius;
	}

	public float mapPointToPathDistance(Vector3 point) {
		float minDistance = 3.402823E+38F;
		float segmentLengthTotal = 0.0F;
		float pathDistance = 0.0F;
		for (int i = 1; i < points.length; i++) {
			segmentLength = lengths[i];
			segmentNormal = normals[i];
			float d = pointToSegmentDistance(point, points[i - 1], points[i]);
			if (d < minDistance) {
				minDistance = d;
				pathDistance = segmentLengthTotal + segmentProjection;
			}
			segmentLengthTotal += segmentLength;
		}

		return pathDistance;
	}

	public void mapPathDistanceToPoint(float pathDistance, Vector3 point) {
		float remainingDistance = pathDistance;
		for (int i = 1; i < points.length; i++) {
			segmentLength = lengths[i];
			if (segmentLength < remainingDistance) {
				remainingDistance -= segmentLength;
			} else {
				float ratio = remainingDistance / segmentLength;
				point.setInterp(ratio, points[i - 1], points[i]);
				return;
			}
		}

	}

	public float pointToSegmentDistance(Vector3 point, Vector3 ep0, Vector3 ep1) {
		local.setDiff(point, ep0);
		segmentProjection = segmentNormal.dot(local);
		if (segmentProjection < 0.0F) {
			chosen.set(ep0);
			segmentProjection = 0.0F;
			return point.approximateDistance(ep0);
		}
		if (segmentProjection > segmentLength) {
			chosen.set(ep1);
			segmentProjection = segmentLength;
			return point.approximateDistance(ep1);
		} else {
			chosen.setScale(segmentProjection, segmentNormal);
			chosen.setSum(chosen, ep0);
			return point.approximateDistance(chosen);
		}
	}

	public PolylinePathway(int radius, Vector3[] points) {
		local = new Vector3();
		chosen = new Vector3();
		segmentNormal = new Vector3();
		this.points = points;
		this.radius = radius;

		cachePathStats();
	}

	public float radius;
	public Vector3 points[];
	private float segmentLength;
	private float segmentProjection;
	private Vector3 local;
	private Vector3 chosen;
	private Vector3 segmentNormal;
	private float lengths[];
	private Vector3 normals[];
}
